// To-Do: Remove printf's
#include <stdio.h>

/**
 * AppleIIGo
 * MMU (Memory Management Unit) class
 * (C) 2008 by Marc S. Ressl (mressl@umich.edu)
 * Released under the GPL
 */

#include "MMU.h"

MMU::MMU(Motherboard * motherboard, UINT32 mmuType) :
Device(motherboard, "MMU") {
	this->mmuType = mmuType;
	
	mainMemoryOffset = OFFSET_FLOATINGBUS;
	mainMemoryByteNum = 0;
	romBank1Offset = OFFSET_FLOATINGBUS;
	romBank2Offset = OFFSET_FLOATINGBUS;
	languageCardOffset = OFFSET_FLOATINGBUS;
	auxMemoryOffset = OFFSET_FLOATINGBUS;
	isAuxMemoryBank0ShadowInhibited = true;
	shadowMemoryOffset = OFFSET_FLOATINGBUS;
	
	initLanguageCard();
}

void MMU::onDevicesConnected() {
	iou = motherboard->getIou();
	memory = motherboard->getMemory();
	isVideoModified = motherboard->getVideoRenderer()->getIsVideoModified();
}

void MMU::onMemoryRequested() {
	floatingBusReadOffset = memory->requestMemory(PAGE_BYTENUM);
	floatingBusWriteOffset = memory->requestMemory(PAGE_BYTENUM);
}

void MMU::onMemoryAllocated() {
	memoryPool = memory->getMemory(0);
	shadowMemory = memory->getMemory(shadowMemoryOffset);
	
	mapIoRange(0x0000, 0xffff, true);
	mapMemoryRange(0x0000, 0xffff, OFFSET_FLOATINGBUS, OFFSET_FLOATINGBUS);
	mapShadowRange(0x0000, 0xffff, true, false);
	
	if (mmuType != MMUTYPE_IIGS)
		return;

	mapMemoryRange(0xe000, 0xe1ff, shadowMemoryOffset, shadowMemoryOffset);
	mapShadowRange(0xe000, 0xe0ff, false, false);
	mapShadowRange(0xe100, 0xe1ff, false, true);
}

void MMU::onResetAsserted(bool isRestart) {
	switch (mmuType) {
		case MMUTYPE_II:
			if (!isRestart)
				return;
			break;
		case MMUTYPE_IIC:
		case MMUTYPE_IICPLUS:
		case MMUTYPE_IIE:
		case MMUTYPE_IIGS:
			initLanguageCard();
			break;
	}
	
	isPage2Value = false;
	isHiresValue = false;
	is80StoreValue = false;
	isAuxReadValue = false;
	isAuxWriteValue = false;
	isAuxZeropageValue = false;
	isRomInternalValue = ((mmuType == MMUTYPE_IIC) ||
						  (mmuType == MMUTYPE_IICPLUS)) ? true : false;
	isRomBank2Value = false;
	
	mapMainMemory();
	setShadowAllBanks(false);
	setShadowBankLatchEnabled(true);
	setShadowRegister(0);
	setDmaBank(0);
}

void MMU::setLanguageCardBank2(bool value) {
	if (isLanguageCardBank2() != value) {
		isLanguageCardBank2Value = value;
		mapLanguageCard();
	}
}

bool MMU::isLanguageCardBank2() {
	return isLanguageCardBank2Value;
}

void MMU::enableLanguageCardRead(bool value) {
	if (isLanguageCardReadEnabled() != value) {
		isLanguageCardReadEnabledValue = value;
		mapLanguageCard();
	}
}

bool MMU::isLanguageCardReadEnabled() {
	return isLanguageCardReadEnabledValue;
}

void MMU::enableLanguageCardWrite(bool value) {
	if (isLanguageCardWriteEnabled() != value) {
		isLanguageCardWriteEnabledValue = value;
		mapLanguageCard();
	}
}

bool MMU::isLanguageCardWriteEnabled() {
	return isLanguageCardWriteEnabledValue;
}

void MMU::setPage2(bool value) {
	if (isPage2() != value) {
		isPage2Value = value;
		mapMainMemory();
		mapShadow(false);
	}
}

bool MMU::isPage2() {
	return isPage2Value;
}

void MMU::setHires(bool value) {
	if (isHires() != value) {
		isHiresValue = value;
		mapMainMemory();
	}
}

bool MMU::isHires() {
	return isHiresValue;
}

void MMU::set80Store(bool value) {
	if (is80Store() != value) {
		is80StoreValue = value;
		mapMainMemory();
		mapShadow(false);
	}
}

bool MMU::is80Store() {
	return is80StoreValue;
}

void MMU::setAuxRead(bool value) {
	if (isAuxRead() != value) {
		isAuxReadValue = value;
		mapMainMemory();
	}
}

bool MMU::isAuxRead() {
	return isAuxReadValue;
}

void MMU::setAuxWrite(bool value) {
	if (isAuxWrite() != value) {
		isAuxWriteValue = value;
		mapMainMemory();
		mapShadow(false);
	}
}

bool MMU::isAuxWrite() {
	return isAuxWriteValue;
}

void MMU::setAuxZeropage(bool value) {
	if (isAuxZeropage() != value) {
		isAuxZeropageValue = value;
		mapLanguageCard();
	}
}

bool MMU::isAuxZeropage() {
	return isAuxZeropageValue;
}

void MMU::setRomInternal(bool value) {
	if (isRomInternal() != value) {
		isRomInternalValue = value;
		mapIo();
	}
}

bool MMU::isRomInternal() {
	return isRomInternalValue;
}

void MMU::setRomBank2(bool value) {
	if (isRomBank2() != value) {
		isRomBank2Value = value;
		mapIo();
		mapLanguageCard();
	}
}

bool MMU::isRomBank2() {
	return isRomBank2Value;
}

void MMU::setStateRegister(UINT8 stateRegister) {
	isRomInternalValue = getBit(stateRegister, STATE_INTCXROM);
	isRomBank2Value = getBit(stateRegister, STATE_ROMBANK);
	isLanguageCardBank2Value = getBit(stateRegister, STATE_LCBNK2);
	isLanguageCardReadEnabledValue = !getBit(stateRegister, STATE_RDROM);
	isAuxWriteValue = getBit(stateRegister, STATE_RAMWRT);
	isAuxReadValue = getBit(stateRegister, STATE_RAMRD);
	isPage2Value = getBit(stateRegister, STATE_PAGE2);
	isAuxZeropageValue = getBit(stateRegister, STATE_ALTZP);
	
	mapMainMemory();
	mapIo();
	mapLanguageCard();
	mapShadow(false);
}

UINT8 MMU::getStateRegister() {
	UINT8 stateRegister = 0;
	setBit(stateRegister, STATE_INTCXROM, isRomInternalValue);
	setBit(stateRegister, STATE_ROMBANK, isRomBank2Value);
	setBit(stateRegister, STATE_LCBNK2, isLanguageCardBank2Value);
	setBit(stateRegister, STATE_RDROM, !isLanguageCardReadEnabledValue);
	setBit(stateRegister, STATE_RAMWRT, isAuxWriteValue);
	setBit(stateRegister, STATE_RAMRD, isAuxReadValue);
	setBit(stateRegister, STATE_PAGE2, isPage2Value);
	setBit(stateRegister, STATE_ALTZP, isAuxZeropageValue);
	return stateRegister;
}

void MMU::setShadowAllBanks(bool value) {
	isShadowAllBanksValue = value;
	mapShadow(true);
}

bool MMU::isShadowAllBanks() {
	return isShadowAllBanksValue;
}

void MMU::setShadowBankLatchEnabled(bool value) {
	isShadowBankLatchEnabledValue = value;
	mapShadow(false);
}

bool MMU::isShadowBankLatchEnabled() {
	return isShadowBankLatchEnabledValue;
}

void MMU::setShadowRegister(UINT8 shadowRegister) {
	this->shadowRegister = shadowRegister;
	
	mapIo();
	mapLanguageCard();
	mapShadow(false);
}

UINT8 MMU::getShadowRegister() {
	return shadowRegister;
}

void MMU::setDmaBank(UINT8 value) {
	dmaBankRegister = value;
}

UINT8 MMU::getDmaBank() {
	return dmaBankRegister;
}

void MMU::initLanguageCard() {
	isLanguageCardBank2Value = true;
	isLanguageCardReadEnabledValue = false;
	isLanguageCardWriteEnabledValue = false;
}

void MMU::setIsVideoModifiedMap(UINT8 * isVideoModifiedMap) {
	this->isVideoModifiedMap = isVideoModifiedMap;
}

void MMU::writeMemory(UINT32 address, UINT8 value) {
	if (isIoAccess[address >> 8])
		iou->writeIo(address, value);
	else {
		memoryPool[address + memoryMapWrite[address >> 8]] = value;
		address += shadowMap[address >> 8];
		shadowMemory[address] = value;
		isVideoModified[isVideoModifiedMap[address]] = true;
	}
}

UINT8 MMU::readMemory(UINT32 address) {
	if (isIoAccess[address >> 8])
		return iou->readIo(address);
	
	return memoryPool[address + memoryMapRead[address >> 8]];
}

UINT8 MMU::readMemoryDirect(UINT32 address) {
	return memoryPool[address + memoryMapRead[address >> 8]];
}

void MMU::setMainMemory(UINT32 mainMemoryOffset, UINT32 mainMemoryByteNum) {
	this->mainMemoryOffset = mainMemoryOffset;
	this->mainMemoryByteNum = mainMemoryByteNum;
	
	if (mainMemoryByteNum >= BANK_BYTENUM)
		setLanguageCardMemory(mainMemoryOffset + OFFSET_LCBANK1);
	
	if (mainMemoryByteNum >= (2 * BANK_BYTENUM))
		setAuxMemory(mainMemoryOffset + BANK_BYTENUM, true);
	
	if (mainMemoryByteNum)
		mapMemoryRange(0x0000, (mainMemoryByteNum - 1) >> 8,
					   mainMemoryOffset, mainMemoryOffset);
	
	mapMainMemory();
	mapIo();
	mapLanguageCard();
}

void MMU::setRom(UINT32 romOffset, UINT32 romBank1Offset,
				 UINT32 romByteNum) {
	this->romBank1Offset = romBank1Offset;
	this->romBank2Offset = romOffset + romByteNum - 0x4000;
	mapIo();
	mapLanguageCard();
	
	if (mmuType == MMUTYPE_IIGS)
		mapMemoryRange(0xffff - ((romByteNum - 1) >> 8), 0xffff,
					   romOffset, romOffset);
}

void MMU::setLanguageCardMemory(UINT32 languageCardOffset) {
	this->languageCardOffset = languageCardOffset;
	
	mapLanguageCard();
}

void MMU::setAuxMemory(UINT32 auxMemoryOffset, bool isAuxBank0) {
	this->auxMemoryOffset = auxMemoryOffset;
	this->isAuxMemoryBank0ShadowInhibited = !isAuxBank0;
	
	mapMainMemory();
	mapLanguageCard();
}

void MMU::setShadowMemory(UINT32 shadowMemoryOffset) {
	this->shadowMemoryOffset = shadowMemoryOffset;
}

UINT32 MMU::getMainMemoryOffset(bool isAux) {
	return isAux ? auxMemoryOffset : mainMemoryOffset;
}

SINT32 MMU::getValidPage(UINT32 pageNum) {
	SINT32 mainMemoryLastPage = mainMemoryByteNum ?
		(mainMemoryByteNum - 1) >> 8 : -1;
	return (((SINT32) pageNum) > mainMemoryLastPage) ?
		mainMemoryLastPage : pageNum;
}

UINT32 MMU::getLanguageCardOffset() {
	return isAuxZeropage() ?
		(auxMemoryOffset + OFFSET_LCBANK1) : languageCardOffset;
}

UINT32 MMU::getRomBankOffset() {
	return isRomBank2() ? romBank2Offset : romBank1Offset;
}

void MMU::mapMainMemory() {
//	printf("mapMainMemory\n");
	UINT32 readOffset = getMainMemoryOffset(isAuxRead()) + OFFSET_RAM1;
	UINT32 writeOffset = getMainMemoryOffset(isAuxWrite()) + OFFSET_RAM1;
	mapMemoryRange(0x0002, getValidPage(0x00bf), readOffset, writeOffset);
	
	if (!is80Store())
		return;

	UINT32 textOffset = getMainMemoryOffset(isPage2()) + OFFSET_TEXT1;
	mapMemoryRange(0x0004, getValidPage(0x0007), textOffset, textOffset);
		
	if (!isHires())
		return;

	UINT32 hiresOffset = getMainMemoryOffset(isPage2()) + OFFSET_HIRES1;
	mapMemoryRange(0x0020, getValidPage(0x003f), hiresOffset, hiresOffset);
}

void MMU::mapIo() {
//	printf("mapIo\n");
	UINT32 romBankOffset = getRomBankOffset();
	UINT32 ioOffsetRead, ioOffsetWrite;

	bool isIoLanguageCardInhibited = getBit(shadowRegister, SHADOW_IOLCINH);
	if (isIoLanguageCardInhibited)
		ioOffsetWrite = ioOffsetRead = mainMemoryOffset + OFFSET_LCBANK1;
	else {
		ioOffsetRead = romBankOffset;
		ioOffsetWrite = OFFSET_FLOATINGBUS;
	}
	
	mapIoRange(0x00c0, 0x00c0, isIoLanguageCardInhibited);
	mapIoRange(0x00c1, 0x00cf, isIoLanguageCardInhibited || isRomInternal());
	mapMemoryRange(0x00c0, 0x00cf, ioOffsetRead, ioOffsetWrite);
	
	if ((mmuType == MMUTYPE_IICPLUS) && (isRomBank2Value))
		mapIoRange(0x00cc, 0x00cf, false);
	
	if (mmuType != MMUTYPE_IIGS)
		return;
	
	mapIoRange(0x01c0, 0x01c0, isIoLanguageCardInhibited);
	mapIoRange(0x01c1, 0x01cf, isIoLanguageCardInhibited || isRomInternal());
	mapMemoryRange(0x01c0, 0x01cf, ioOffsetRead, ioOffsetWrite);
	
	mapIoRange(0xe0c0, 0xe0c0, false);
	mapIoRange(0xe0c1, 0xe0cf, isRomInternal());
	mapMemoryRange(0xe0c0, 0xe0cf, ioOffsetRead, ioOffsetWrite);
	mapIoRange(0xe0c0, 0xe0c0, false);
	mapIoRange(0xe1c1, 0xe1cf, isRomInternal());
	mapMemoryRange(0xe1c0, 0xe1cf, ioOffsetRead, ioOffsetWrite);
}

void MMU::mapLanguageCard() {
//	printf("mapLanguageCard\n");
	UINT32 memoryZeroPageOffset =
		getMainMemoryOffset(isAuxZeropage()) + OFFSET_ZEROPAGE;
	mapMemoryRange(0x0000, 0x0001, memoryZeroPageOffset, memoryZeroPageOffset);
	
	bool isIoLanguageCardInhibited = getBit(shadowRegister, SHADOW_IOLCINH);
	if (isIoLanguageCardInhibited) {
		UINT32 memoryOffset = mainMemoryOffset + OFFSET_LCBANK2;
		mapMemoryRange(0x00d0, 0x00ff, memoryOffset, memoryOffset);
	} else {
		UINT32 memoryD000Read, memoryD000Write;
		UINT32 memoryE000Read, memoryE000Write;
		if (isLanguageCardReadEnabled()) {
			memoryD000Read = getLanguageCardOffset() +
				(isLanguageCardBank2() ? 0x1000 : 0);
			memoryE000Read = getLanguageCardOffset() + 0x2000;
		} else {
			UINT32 romBankOffset = getRomBankOffset();
			memoryD000Read = romBankOffset + 0x1000;
			memoryE000Read = romBankOffset + 0x2000;
		}
		if (isLanguageCardWriteEnabled()) {
			memoryD000Write = getLanguageCardOffset() +
				(isLanguageCardBank2() ? 0x1000 : 0);
			memoryE000Write = getLanguageCardOffset() + 0x2000;
		} else {
			memoryD000Write = OFFSET_FLOATINGBUS;
			memoryE000Write = OFFSET_FLOATINGBUS;
		}
		
		mapMemoryRange(0x00d0, 0x00df, memoryD000Read, memoryD000Write);
		mapMemoryRange(0x00e0, 0x00ff, memoryE000Read, memoryE000Write);
	}
	
	if (mmuType != MMUTYPE_IIGS)
		return;
	
	UINT32 shadowD000 = shadowMemoryOffset +
		(isLanguageCardBank2() ? OFFSET_LCBANK2 : OFFSET_LCBANK1);
	
	mapMemoryRange(0xe0d0, 0xe0df, shadowD000, shadowD000);
	shadowD000 += BANK_BYTENUM;
	mapMemoryRange(0xe1d0, 0xe1df, shadowD000, shadowD000);
}

void MMU::mapShadow(bool isUpdateAllBanks) {
//	printf("mapShadow\n");
	bool isText1Aux = is80Store() ? isPage2() : isAuxWrite();
	bool isText2Aux = isAuxWrite();
	bool isHires1Aux = (is80Store() && isHires()) ? isPage2() : isAuxWrite();
	bool isHires2Aux = isAuxWrite();
	
	bool isText1Inhibited = getBit(shadowRegister, SHADOW_TEXT1INH);
	bool isText2Inhibited = getBit(shadowRegister, SHADOW_TEXT2INH);
	bool isHires1Inhibited = getBit(shadowRegister, SHADOW_HIRES1INH);
	bool isHires2Inhibited = getBit(shadowRegister, SHADOW_HIRES2INH);
	bool isSHiresInhibited = getBit(shadowRegister, SHADOW_SHIRESINH);
	bool isAuxHiresInhibited = getBit(shadowRegister, SHADOW_AUXHIRESINH);
	
	UINT32 bankEnd = (isUpdateAllBanks || isShadowAllBanks()) ? 0xe0 : 0x2;
	for (UINT32 bankIndex = 0; bankIndex < bankEnd; bankIndex += 2) {
		mapShadowRange(bankIndex | 0x0004, bankIndex | 0x0007,
					   isText1Inhibited || 
					   (isText1Aux && isAuxMemoryBank0ShadowInhibited),
					   isText1Aux);
		mapShadowRange(bankIndex | 0x0008, bankIndex | 0x000b,
					   isText2Inhibited || 
					   (isText2Aux && isAuxMemoryBank0ShadowInhibited),
					   isText2Aux);
		mapShadowRange(bankIndex | 0x0020, bankIndex | 0x003f,
					   isHires1Inhibited || 
					   (isHires1Aux && isAuxMemoryBank0ShadowInhibited),
					   isHires1Aux);
		mapShadowRange(bankIndex | 0x0040, bankIndex | 0x005f,
					   isHires2Inhibited || 
					   (isHires2Aux && isAuxMemoryBank0ShadowInhibited),
					   isHires2Aux);
		mapShadowRange(bankIndex | 0x0104, bankIndex | 0x0107,
					   isText1Inhibited, true);
		mapShadowRange(bankIndex | 0x0108, bankIndex | 0x010b,
					   isText2Inhibited, true);
		mapShadowRange(bankIndex | 0x0120, bankIndex | 0x013f,
					   isSHiresInhibited || isAuxHiresInhibited || 
					   isHires1Inhibited, true);
		mapShadowRange(bankIndex | 0x0140, bankIndex | 0x015f,
					   isSHiresInhibited || isAuxHiresInhibited || 
					   isHires2Inhibited, true);
		mapShadowRange(bankIndex | 0x0160, bankIndex | 0x019f,
					   isSHiresInhibited, true);
		if (!isShadowAllBanks())
			isText1Inhibited = isText2Inhibited = isHires1Inhibited = 
			isHires2Inhibited =	isAuxHiresInhibited = isSHiresInhibited = true;
		isText1Aux = isText2Aux = isHires1Aux = isHires2Aux = true;
	}
}

void MMU::mapIoRange(SINT32 pageStart, SINT32 pageEnd, bool isIoInhibited) {
	for (SINT32 pageIndex = pageStart; pageIndex <= pageEnd; pageIndex++)
		this->isIoAccess[pageIndex] = !isIoInhibited;
}

void MMU::mapMemoryRange(SINT32 pageStart, SINT32 pageEnd,
						 UINT32 offsetRead, UINT32 offsetWrite) {
	UINT32 offsetAdjust = pageStart << 8;
	
	for (SINT32 pageIndex = pageStart; pageIndex <= pageEnd; pageIndex++) {
		memoryMapRead[pageIndex] = (offsetRead & OFFSET_FLOATINGBUS) ?
		floatingBusReadOffset - (pageIndex << 8) : offsetRead - offsetAdjust;
		memoryMapWrite[pageIndex] = (offsetWrite & OFFSET_FLOATINGBUS) ?
		floatingBusWriteOffset - (pageIndex << 8) : offsetWrite - offsetAdjust;
	}
}

void MMU::mapShadowRange(SINT32 pageStart, SINT32 pageEnd,
						 bool isShadowingInhibited, bool isShadowBank2) {
	if (!isShadowBankLatchEnabled())
		isShadowBank2 = false;
	
	UINT32 offsetShadow = isShadowBank2 ? SHADOWMEMORY_AUX : SHADOWMEMORY_MAIN;
	UINT32 offsetAdjust = (pageStart & 0xff00) << 8;
	
	for (SINT32 pageIndex = pageStart; pageIndex <= pageEnd; pageIndex++) {
		shadowMap[pageIndex] = isShadowingInhibited ?
		SHADOWMEMORY_FLOATINGBUS - (pageIndex << 8) :
		offsetShadow - offsetAdjust;
	}
}
